<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

    <title>Noise Shader — Alien.js</title>

    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
    <link rel="stylesheet" href="../assets/css/style.css">

    <script type="module">
        import { ColorManagement, GLSL3, LinearSRGBColorSpace, Mesh, NoBlending, OrthographicCamera, PanelItem, RawShaderMaterial, UI, Vector2, WebGLRenderer, getFullscreenTriangle, ticker } from '../../build/alien.three.js';

        // Based on https://codepen.io/vaalentin/pen/MKMmXG

        import simplex2d from '../../src/shaders/modules/noise/simplex2d.glsl.js';

        class NoiseMaterial extends RawShaderMaterial {
            constructor() {
                super({
                    glslVersion: GLSL3,
                    uniforms: {
                        uScale: { value: 2 },
                        uSpeed: { value: 0.5 },
                        uTime: WorldController.time
                    },
                    vertexShader: /* glsl */ `
                        in vec3 position;
                        in vec2 uv;

                        out vec2 vUv;

                        void main() {
                            vUv = uv;

                            gl_Position = vec4(position, 1.0);
                        }
                    `,
                    fragmentShader: /* glsl */ `
                        precision highp float;

                        uniform float uScale;
                        uniform float uSpeed;
                        uniform float uTime;

                        in vec2 vUv;

                        out vec4 FragColor;

                        ${simplex2d}

                        float fbm(vec2 pos) {
                            float a = sin(uTime * uSpeed);
                            float b = cos(uTime * uSpeed);
                            mat2 m = mat2(-0.80, 0.36, -0.60, -0.48);

                            float total;

                            total = 0.5000 * snoise(pos) * a;
                            pos = m * pos * 2.02;

                            total += 0.2500 * snoise(pos) * b;
                            pos = m * pos * 2.03;

                            total += 0.1250 * snoise(pos) * a;
                            pos = m * pos * 2.01;

                            total += 0.0625 * snoise(pos) * b;

                            return total;
                        }

                        void main() {
                            vec2 pos = vUv.xy;

                            vec2 q = vec2(fbm(pos), fbm(pos));

                            float c = fbm(pos + sin(uTime * uSpeed) * uScale * q);

                            FragColor = vec4(vec3(c), 1.0);
                        }
                    `,
                    blending: NoBlending,
                    depthTest: false,
                    depthWrite: false
                });
            }
        }

        class PanelController {
            static init(ui) {
                this.ui = ui;

                this.initPanel();
            }

            static initPanel() {
                const { screen } = RenderManager;

                const items = [
                    {
                        name: 'FPS'
                    },
                    {
                        type: 'divider'
                    },
                    {
                        type: 'slider',
                        name: 'Multiply',
                        min: -5,
                        max: 5,
                        step: 0.01,
                        value: screen.material.uniforms.uScale.value,
                        callback: value => {
                            screen.material.uniforms.uScale.value = value;
                        }
                    },
                    {
                        type: 'slider',
                        name: 'Speed',
                        min: 0,
                        max: 1,
                        step: 0.01,
                        value: screen.material.uniforms.uSpeed.value,
                        callback: value => {
                            screen.material.uniforms.uSpeed.value = value;
                        }
                    }
                ];

                items.forEach(data => {
                    this.ui.addPanel(new PanelItem(data));
                });
            }
        }

        class RenderManager {
            static init(renderer, screen, screenCamera) {
                this.renderer = renderer;
                this.screen = screen;
                this.screenCamera = screenCamera;

                this.initRenderer();
            }

            static initRenderer() {
                this.screen.material = new NoiseMaterial();
            }

            // Public methods

            static resize = (width, height, dpr) => {
                this.renderer.setPixelRatio(dpr);
                this.renderer.setSize(width, height);
            };

            static update = () => {
                this.renderer.render(this.screen, this.screenCamera);
            };
        }

        class WorldController {
            static init() {
                this.initWorld();

                this.addListeners();
            }

            static initWorld() {
                this.renderer = new WebGLRenderer({
                    powerPreference: 'high-performance'
                });

                // Output canvas
                this.element = this.renderer.domElement;

                // Disable color management
                ColorManagement.enabled = false;
                this.renderer.outputColorSpace = LinearSRGBColorSpace;

                // Fullscreen triangle
                this.screenCamera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
                this.screenTriangle = getFullscreenTriangle();
                this.screen = new Mesh(this.screenTriangle, this.material);
                this.screen.frustumCulled = false;

                // Global uniforms
                this.resolution = { value: new Vector2() };
                this.texelSize = { value: new Vector2() };
                this.aspect = { value: 1 };
                this.time = { value: 0 };
                this.frame = { value: 0 };
            }

            static addListeners() {
                this.renderer.domElement.addEventListener('touchstart', this.onTouchStart);
            }

            // Event handlers

            static onTouchStart = e => {
                e.preventDefault();
            };

            // Public methods

            static resize = (width, height, dpr) => {
                width = Math.round(width * dpr);
                height = Math.round(height * dpr);

                this.resolution.value.set(width, height);
                this.texelSize.value.set(1 / width, 1 / height);
                this.aspect.value = width / height;
            };

            static update = (time, delta, frame) => {
                this.time.value = time;
                this.frame.value = frame;
            };
        }

        class App {
            static async init() {
                this.initWorld();
                this.initViews();
                this.initControllers();

                this.addListeners();
                this.onResize();

                this.initPanel();
            }

            static initWorld() {
                WorldController.init();
                document.body.appendChild(WorldController.element);
            }

            static initViews() {
                this.ui = new UI({ fps: true });
                this.ui.animateIn();
                document.body.appendChild(this.ui.element);
            }

            static initControllers() {
                const { renderer, screen, screenCamera } = WorldController;

                RenderManager.init(renderer, screen, screenCamera);
            }

            static initPanel() {
                PanelController.init(this.ui);
            }

            static addListeners() {
                window.addEventListener('resize', this.onResize);
                ticker.add(this.onUpdate);
                ticker.start();
            }

            // Event handlers

            static onResize = () => {
                const width = document.documentElement.clientWidth;
                const height = document.documentElement.clientHeight;
                const dpr = window.devicePixelRatio;

                WorldController.resize(width, height, dpr);
                RenderManager.resize(width, height, dpr);
            };

            static onUpdate = (time, delta, frame) => {
                WorldController.update(time, delta, frame);
                RenderManager.update(time, delta, frame);
                this.ui.update();
            };
        }

        App.init();
    </script>
</head>
<body>
</body>
</html>
